"""
配置文件自动同步服务
当从乐问学平台获取学校信息后，自动同步更新所有相关配置文件，并拉取运行相应的Docker镜像
"""

import json
import logging
import shutil
import subprocess
import docker
import os
import hashlib
import time
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, Optional, Tuple, List
from .gitee_config_service import GiteeConfigService
from .image_update_status import get_status_manager

logger = logging.getLogger(__name__)

class ConfigSyncService:
    """配置文件同步服务"""
    
    def __init__(self, socketio=None):
        self.project_root = Path(__file__).parent.parent.parent.parent
        self.config_dir = self.project_root / "config"
        self.backend_data_dir = self.project_root / "backend" / "data"
        self.image_cache_file = Path("/tmp/image_update_cache.json")
        self.socketio = socketio  # 保存socketio实例
        self.load_image_cache()
        
        # 初始化Docker客户端
        try:
            self.docker_client = docker.from_env()
            self.docker_client.ping()
            logger.info("Docker客户端初始化成功")
        except Exception as e:
            logger.error(f"Docker客户端初始化失败: {e}")
            self.docker_client = None
        
    def load_image_cache(self):
        """加载镜像更新缓存"""
        try:
            if self.image_cache_file.exists():
                with open(self.image_cache_file, 'r', encoding='utf-8') as f:
                    self.image_cache = json.load(f)
            else:
                self.image_cache = {}
        except Exception as e:
            logger.warning(f"加载镜像缓存失败: {e}")
            self.image_cache = {}
    
    def save_image_cache(self):
        """保存镜像更新缓存"""
        try:
            with open(self.image_cache_file, 'w', encoding='utf-8') as f:
                json.dump(self.image_cache, f, indent=2, ensure_ascii=False)
        except Exception as e:
            logger.error(f"保存镜像缓存失败: {e}")
    
    def check_image_updates(self, image_name: str, force_check: bool = False) -> Tuple[bool, str, str]:
        """智能检查镜像是否需要更新，返回(需要更新, 原因, 操作类型)"""
        try:
            logger.info(f"🔍 检查镜像更新: {image_name}")
            
            # 检查缓存
            cache_key = hashlib.md5(image_name.encode()).hexdigest()
            last_check = self.image_cache.get(cache_key, {})
            current_time = time.time()
            
            # 如果不是强制检查且距离上次检查不到30分钟，跳过检查
            if not force_check and last_check.get("last_check_time"):
                time_diff = current_time - last_check["last_check_time"]
                if time_diff < 1800:  # 30分钟
                    logger.info(f"⏭️ 距离上次检查仅{int(time_diff/60)}分钟，跳过检查")
                    return False, "距离上次检查时间过短", "none"
            
            # 获取本地镜像信息
            local_info = self._get_local_image_info(image_name)
            if not local_info:
                logger.info("📥 本地镜像不存在，需要拉取")
                # 执行带进度的镜像拉取
                self._pull_image_with_progress(image_name)
                return True, "本地镜像不存在", "replace"
            
            local_image_id = local_info["id"]
            
            # 检查镜像名称是否匹配（用于判断replace还是update）
            local_repo_tags = local_info.get("repo_tags", [])
            expected_tag = image_name
            
            # 如果本地镜像的标签不包含期望的镜像名，说明需要替换
            is_name_match = any(expected_tag in tag for tag in local_repo_tags)
            if not is_name_match:
                logger.info(f"🔄 镜像名称不匹配，需要替换: 本地={local_repo_tags}, 期望={expected_tag}")
                return True, "镜像名称不匹配", "replace"
            
            # 尝试获取远程镜像摘要进行快速比较
            try:
                result = subprocess.run([
                    "docker", "manifest", "inspect", image_name, "--verbose"
                ], capture_output=True, text=True, timeout=30)
                
                if result.returncode == 0:
                    manifest_data = json.loads(result.stdout)
                    if isinstance(manifest_data, list) and len(manifest_data) > 0:
                        remote_digest = manifest_data[0].get("Descriptor", {}).get("digest")
                    elif isinstance(manifest_data, dict):
                        remote_digest = manifest_data.get("Descriptor", {}).get("digest")
                    else:
                        remote_digest = None
                    
                    if remote_digest:
                        local_digest = local_info.get("digest")
                        if local_digest and remote_digest in local_digest:
                            logger.info("✅ 摘要匹配，镜像是最新的")
                            # 更新缓存
                            self.image_cache[cache_key] = {
                                "image_name": image_name,
                                "last_check_time": current_time,
                                "local_image_id": local_image_id,
                                "status": "up_to_date"
                            }
                            self.save_image_cache()
                            return False, "摘要匹配，无需更新", "none"
                        else:
                            logger.info("🔄 摘要不匹配，需要更新镜像版本")
                            return True, "镜像版本过旧", "update"
            except Exception as e:
                logger.warning(f"摘要检查失败，使用拉取检查: {e}")
            
            # 如果摘要检查失败，使用拉取测试（最可靠但较慢）
            logger.info("🔄 执行拉取测试检查更新...")
            self._pull_image_with_progress(image_name)
            
            # 重新获取镜像信息
            new_local_info = self._get_local_image_info(image_name)
            if new_local_info:
                new_image_id = new_local_info["id"]
                
                if local_image_id != new_image_id:
                    logger.info(f"🆕 检测到镜像更新!")
                    logger.info(f"   旧镜像ID: {local_image_id[:12]}...")
                    logger.info(f"   新镜像ID: {new_image_id[:12]}...")
                    
                    # 更新缓存
                    self.image_cache[cache_key] = {
                        "image_name": image_name,
                        "last_check_time": current_time,
                        "local_image_id": new_image_id,
                        "status": "updated"
                    }
                    self.save_image_cache()
                    
                    return True, f"镜像已更新 ({local_image_id[:12]} -> {new_image_id[:12]})", "update"
                else:
                    logger.info("✅ 镜像已是最新版本")
                    # 更新缓存
                    self.image_cache[cache_key] = {
                        "image_name": image_name,
                        "last_check_time": current_time,
                        "local_image_id": local_image_id,
                        "status": "up_to_date"
                    }
                    self.save_image_cache()
                    return False, "镜像已是最新版本", "none"
            else:
                return False, "拉取失败，无法获取镜像信息", "none"
                
        except Exception as e:
            logger.error(f"检查镜像更新失败: {e}")
            return False, f"检查失败: {str(e)}", "none"
    
    def _get_local_image_info(self, image_name: str) -> Optional[Dict[str, Any]]:
        """获取本地镜像信息"""
        try:
            result = subprocess.run([
                "docker", "inspect", image_name
            ], capture_output=True, text=True, timeout=30)
            
            if result.returncode == 0:
                inspect_data = json.loads(result.stdout)[0]
                return {
                    "id": inspect_data["Id"],
                    "created": inspect_data["Created"],
                    "size": inspect_data["Size"],
                    "digest": inspect_data.get("RepoDigests", [None])[0] if inspect_data.get("RepoDigests") else None
                }
            else:
                return None
        except Exception as e:
            logger.error(f"获取本地镜像信息失败: {e}")
            return None
        
    def _pull_image_with_progress(self, image_name: str):
        """带进度监控的镜像拉取"""
        try:
            # 获取状态管理器
            status_manager = get_status_manager(self.socketio)
            
            logger.info(f"🚀 开始拉取镜像: {image_name}")
            status_manager.update_download_progress(0, f"开始拉取镜像: {image_name}")
            
            # 使用Docker Python API进行拉取，这样可以获取进度
            if self.docker_client:
                try:
                    # 拆分镜像名和标签
                    if ':' in image_name:
                        repo, tag = image_name.rsplit(':', 1)
                    else:
                        repo, tag = image_name, 'latest'
                    
                    logger.info(f"📥 拉取镜像: {repo}:{tag}")
                    
                    # 使用低级API来获取拉取进度
                    pull_generator = self.docker_client.api.pull(repo, tag=tag, stream=True, decode=True)
                    
                    layers_status = {}
                    total_layers = 0
                    completed_layers = 0
                    
                    for line in pull_generator:
                        if 'id' in line:
                            layer_id = line['id']
                            
                            if 'status' in line:
                                status = line['status']
                                layers_status[layer_id] = status
                                
                                # 计算总进度
                                if status == 'Pull complete':
                                    if layer_id not in [l for l, s in layers_status.items() if s == 'Pull complete']:
                                        completed_layers += 1
                                elif status.startswith('Pulling fs layer'):
                                    if layer_id not in layers_status or layers_status[layer_id] != 'Pulling fs layer':
                                        total_layers += 1
                                
                                # 计算百分比
                                if total_layers > 0:
                                    progress = min(int((completed_layers / total_layers) * 100), 99)
                                    status_manager.update_download_progress(
                                        progress, 
                                        f"拉取镜像层 {completed_layers}/{total_layers}: {status}"
                                    )
                                    
                                    logger.info(f"📊 拉取进度: {progress}% ({completed_layers}/{total_layers})")
                    
                    # 拉取完成
                    status_manager.update_download_progress(100, f"镜像拉取完成: {image_name}")
                    logger.info(f"✅ 镜像拉取完成: {image_name}")
                    
                except Exception as e:
                    logger.error(f"Docker API拉取失败，使用命令行方式: {e}")
                    # 回退到命令行方式
                    self._pull_image_with_subprocess(image_name, status_manager)
            else:
                # 没有Docker客户端，使用命令行方式
                self._pull_image_with_subprocess(image_name, status_manager)
                
        except Exception as e:
            logger.error(f"拉取镜像失败: {e}")
            status_manager = get_status_manager(self.socketio)
            status_manager.update_download_progress(0, f"拉取失败: {str(e)}")
            raise
    
    def _pull_image_with_subprocess(self, image_name: str, status_manager):
        """使用subprocess拉取镜像（回退方案）"""
        try:
            import subprocess
            import threading
            import time
            
            logger.info(f"🔄 使用subprocess拉取镜像: {image_name}")
            
            # 启动docker pull进程
            process = subprocess.Popen([
                "docker", "pull", image_name
            ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1)
            
            # 实时进度更新
            progress = 0
            while process.poll() is None:
                time.sleep(2)
                progress = min(progress + 10, 90)
                status_manager.update_download_progress(progress, f"正在拉取镜像: {image_name}")
            
            # 等待完成
            stdout, stderr = process.communicate()
            
            if process.returncode == 0:
                status_manager.update_download_progress(100, f"镜像拉取完成: {image_name}")
                logger.info(f"✅ 镜像拉取完成: {image_name}")
            else:
                logger.error(f"镜像拉取失败: {stderr}")
                status_manager.update_download_progress(0, f"拉取失败: {stderr}")
                raise Exception(f"Docker pull failed: {stderr}")
                
        except Exception as e:
            logger.error(f"subprocess拉取镜像失败: {e}")
            raise
        
    def sync_all_configs(self, school_name: str, school_config: Dict[str, Any]) -> bool:
        """
        同步所有配置文件
        
        Args:
            school_name: 学校名称
            school_config: 从Gitee获取的学校配置
            
        Returns:
            同步是否成功
        """
        try:
            logger.info(f"开始同步学校配置: {school_name}")
            
            # 保存当前学校名称供其他服务使用
            self._save_current_school(school_name)
            
            # 1. 更新 config/update.json
            self._update_update_json(school_config)
            
            # 2. 更新 backend/data/docker_images_config.json
            self._update_docker_images_config(school_config)
            
            # 3. 更新 backend/data/remote_docker_config.json
            self._update_remote_docker_config(school_config)
            
            # 4. 创建/更新学校专用配置文件
            self._create_school_config(school_name, school_config)
            
            # 5. 获取并更新Docker Compose文件
            self._sync_docker_compose(school_name, school_config)
            
            # 6. 根据Gitee配置自动拉取Docker镜像
            self._pull_docker_images_from_config(school_config)
            
            # 7. 自动拉取和运行Docker镜像
            self._pull_and_run_docker_images(school_config)
            
            logger.info(f"配置同步完成: {school_name}")
            return True
            
        except Exception as e:
            logger.error(f"配置同步失败: {e}")
            return False
    
    def _save_current_school(self, school_name: str):
        """保存当前学校名称供其他服务使用"""
        try:
            school_info = {
                "school_name": school_name,
                "updated_at": datetime.now().isoformat()
            }
            school_config_file = self.config_dir / "current_school.json"
            with open(school_config_file, 'w', encoding='utf-8') as f:
                json.dump(school_info, f, indent=2, ensure_ascii=False)
            logger.info(f"已保存当前学校配置: {school_name}")
        except Exception as e:
            logger.error(f"保存当前学校配置失败: {e}")
    
    def _update_update_json(self, school_config: Dict[str, Any]):
        """更新 config/update.json"""
        update_file = self.config_dir / "update.json"
        
        try:
            # 读取现有配置
            if update_file.exists():
                with open(update_file, 'r', encoding='utf-8') as f:
                    update_config = json.load(f)
            else:
                update_config = {"docker": {"services": []}}
            
            # 更新Docker服务配置
            if "docker" in school_config and "services" in school_config["docker"]:
                update_config["docker"] = school_config["docker"]
            
            # 写入更新的配置
            with open(update_file, 'w', encoding='utf-8') as f:
                json.dump(update_config, f, indent=2, ensure_ascii=False)
            
            logger.info(f"已更新 {update_file}")
            
        except Exception as e:
            logger.error(f"更新 update.json 失败: {e}")
            raise
    
    def _update_docker_images_config(self, school_config: Dict[str, Any]):
        """更新 backend/data/docker_images_config.json"""
        images_file = self.backend_data_dir / "docker_images_config.json"
        
        try:
            # 构建镜像配置
            images_config = {
                "remote_config_url": "file:///home/mobox/Documents/mobox_dashboard/backend/data/remote_docker_config.json",
                "images": [],
                "last_updated": "2025-06-27T16:30:00.000000"
            }
            
            # 从学校配置中提取镜像信息
            if "docker" in school_config and "services" in school_config["docker"]:
                registry = school_config["docker"]["registry"]["url"]
                namespace = school_config["docker"]["registry"]["namespace"]
                
                for service in school_config["docker"]["services"]:
                    if service.get("enabled", True):
                        image_config = {
                            "name": service["remote_image"],
                            "registry": registry,
                            "current_version": "latest",
                            "auto_update": True,
                            "enabled": True,
                            "description": service.get("description", "")
                        }
                        images_config["images"].append(image_config)
            
            # 写入配置
            with open(images_file, 'w', encoding='utf-8') as f:
                json.dump(images_config, f, indent=2, ensure_ascii=False)
            
            logger.info(f"已更新 {images_file}")
            
        except Exception as e:
            logger.error(f"更新 docker_images_config.json 失败: {e}")
            raise
    
    def _update_remote_docker_config(self, school_config: Dict[str, Any]):
        """更新 backend/data/remote_docker_config.json"""
        remote_file = self.backend_data_dir / "remote_docker_config.json"
        
        try:
            remote_config = {
                "version": "1.0",
                "description": "阿里云Docker镜像更新配置",
                "last_updated": "2025-06-22T23:50:00Z",
                "images": [],
                "metadata": {
                    "update_frequency": "daily",
                    "maintainer": "MoBox Dashboard Team",
                    "contact": "support@mobox.com",
                    "documentation": "https://docs.mobox.com/docker-images"
                }
            }
            
            # 从学校配置中构建镜像信息
            if "docker" in school_config and "services" in school_config["docker"]:
                registry = school_config["docker"]["registry"]["url"]
                
                for service in school_config["docker"]["services"]:
                    if service.get("enabled", True):
                        image_info = {
                            "name": service["remote_image"],
                            "registry": registry,
                            "latest_version": "latest",
                            "description": service.get("description", ""),
                            "size": "2.32GB",
                            "available": True,
                            "release_notes": "自动同步配置",
                            "security_updates": True,
                            "tags": ["latest"],
                            "auto_update": True
                        }
                        remote_config["images"].append(image_info)
            
            # 写入配置
            with open(remote_file, 'w', encoding='utf-8') as f:
                json.dump(remote_config, f, indent=2, ensure_ascii=False)
            
            logger.info(f"已更新 {remote_file}")
            
        except Exception as e:
            logger.error(f"更新 remote_docker_config.json 失败: {e}")
            raise
    
    def _create_school_config(self, school_name: str, school_config: Dict[str, Any]):
        """创建学校专用配置文件"""
        school_file = self.config_dir / f"{school_name}.json"
        
        try:
            with open(school_file, 'w', encoding='utf-8') as f:
                json.dump(school_config, f, indent=2, ensure_ascii=False)
            
            logger.info(f"已创建学校配置: {school_file}")
            
        except Exception as e:
            logger.error(f"创建学校配置失败: {e}")
            raise
    
    def _sync_docker_compose(self, school_name: str, school_config: Dict[str, Any]):
        """同步Docker Compose文件，优先使用缓存"""
        try:
            # 首先检查缓存的compose文件
            cached_compose_file = Path(f"/tmp/gitee_configs/docker-compose-{school_name}.yml")
            local_compose_file = self.config_dir / f"docker-compose-{school_name}.yml"
            
            logger.info(f"📋 [SYNC] 同步Docker Compose文件")
            logger.info(f"📋 [SYNC] 缓存文件: {cached_compose_file}")
            logger.info(f"📋 [SYNC] 目标文件: {local_compose_file}")
            
            if cached_compose_file.exists():
                logger.info(f"📋 使用缓存的Docker Compose文件: {cached_compose_file}")
                try:
                    # 从缓存复制到本地配置目录
                    with open(cached_compose_file, 'r', encoding='utf-8') as f:
                        compose_content = f.read()
                    
                    # 记录compose文件内容摘要
                    lines = compose_content.split('\n')
                    logger.info(f"📋 [SYNC] Compose文件行数: {len(lines)}")
                    
                    # 分析compose文件中的服务
                    if 'services:' in compose_content:
                        import re
                        # 简单解析服务名称
                        service_pattern = r'^  ([^:]+):$'
                        services = []
                        for line in lines:
                            match = re.match(service_pattern, line)
                            if match and not line.strip().startswith('#'):
                                services.append(match.group(1))
                        
                        if services:
                            logger.info(f"📋 [SYNC] 发现服务: {services}")
                            
                        # 分析镜像配置
                        image_pattern = r'^\s+image:\s+(.+)$'
                        images = []
                        for line in lines:
                            match = re.match(image_pattern, line)
                            if match:
                                images.append(match.group(1).strip())
                        
                        if images:
                            logger.info(f"📋 [SYNC] 镜像配置:")
                            for i, img in enumerate(images):
                                logger.info(f"  └─ 镜像{i+1}: {img}")
                    
                    with open(local_compose_file, 'w', encoding='utf-8') as f:
                        f.write(compose_content)
                    
                    logger.info(f"✅ 已复制缓存的Docker Compose文件到: {local_compose_file}")
                    logger.info(f"📊 [SYNC] 文件大小: {len(compose_content)} 字符")
                    return
                except Exception as e:
                    logger.warning(f"⚠️ 使用缓存文件失败: {e}，尝试从远程获取")
            
            # 缓存不存在或失效，从远程获取
            logger.info(f"📡 缓存不存在，从远程获取Docker Compose文件...")
            from .gitee_config_service import create_gitee_config_service
            gitee_service = create_gitee_config_service()
            
            # 按照用户要求的路径格式：config/docker-compose-测试学校.yml
            compose_file_path = f"config/docker-compose-{school_name}.yml"
            logger.info(f"📡 [SYNC] 远程文件路径: {compose_file_path}")
            
            try:
                logger.info(f"🌐 [SYNC] 开始从远程获取compose文件...")
                compose_content = gitee_service._get_file_content(compose_file_path)
                
                logger.info(f"📥 [SYNC] 远程文件获取成功，大小: {len(compose_content)} 字符")
                
                # 保存到本地
                with open(local_compose_file, 'w', encoding='utf-8') as f:
                    f.write(compose_content)
                
                logger.info(f"✅ 已同步Docker Compose文件: {local_compose_file}")
                
            except Exception as e:
                logger.error(f"❌ 从Gitee获取Docker Compose文件失败: {e}")
                raise Exception(f"Docker Compose文件不存在或无法获取: config/docker-compose-{school_name}.yml")
                
        except Exception as e:
            logger.error(f"❌ 同步Docker Compose文件失败: {e}")
            raise  # 不再自动生成，直接抛出异常
    
    def _generate_docker_compose_file(self, school_name: str, school_config: Dict[str, Any]):
        """只从远程仓库同步compose文件，不再支持本地生成"""
        try:
            self._sync_docker_compose(school_name, school_config)
        except Exception as e:
            # 按用户要求：如果从远程获取失败，直接失败，不再本地生成
            raise Exception(f"无法从远程仓库获取Docker Compose文件: config/docker-compose-{school_name}.yml - {e}")
    
    def _pull_docker_images_from_config(self, school_config: Dict[str, Any]):
        """基于LWX平台返回数据的正确更新流程"""
        try:
            logger.info("🚀 [CONFIG_SYNC] 开始基于LWX平台数据的镜像更新流程")
            logger.info(f"📋 [CONFIG_SYNC] 接收到的学校配置键: {list(school_config.keys())}")
            
            # 获取状态管理器
            status_manager = get_status_manager(self.socketio)
            logger.info(f"🎯 [CONFIG_SYNC] 状态管理器已获取，开始检查")
            status_manager.start_checking()
            
            # 步骤1: 解析LWX平台返回的镜像信息
            logger.info("1️⃣ [CONFIG_SYNC] 步骤1/5: 解析LWX平台返回的镜像信息")
            status_manager.update_check_progress(10, "步骤1/5: 解析LWX平台返回的镜像信息")
            
            if "docker" not in school_config:
                msg = "LWX平台返回数据中缺少Docker配置信息"
                logger.error(f"❌ [CONFIG_SYNC] {msg}")
                status_manager.finish_checking([], msg)
                return
            
            registry_url = school_config["docker"]["registry"]["url"]
            services = school_config["docker"]["services"]
            enabled_services = [s for s in services if s.get("enabled", True)]
            total_services = len(enabled_services)
            
            logger.info(f"🏭 [CONFIG_SYNC] 注册表URL: {registry_url}")
            logger.info(f"📦 [CONFIG_SYNC] 总服务数: {len(services)}, 启用服务数: {total_services}")
            status_manager.update_check_progress(20, f"解析完成，找到 {total_services} 个启用服务")
            
            # 步骤2: 校验当前镜像版本
            logger.info("2️⃣ [CONFIG_SYNC] 步骤2/5: 校验当前镜像版本")
            status_manager.update_check_progress(25, "步骤2/5: 校验当前镜像版本")
            
            image_verification_results = []
            for i, service in enumerate(enabled_services, 1):
                service_name = service.get('name', 'Unknown')
                remote_image = service["remote_image"]
                expected_image = f"{registry_url}/{remote_image}"
                
                logger.info(f"📋 [CONFIG_SYNC] [{i}/{total_services}] 校验镜像: {service_name} -> {expected_image}")
                status_manager.update_check_progress(
                    25 + (i * 15 // total_services), 
                    f"步骤2/5: [{i}/{total_services}] 校验 {service_name}"
                )
                
                # 检查镜像是否存在且版本是否正确
                needs_action, action_type, reason = self._verify_image_version(expected_image)
                image_verification_results.append({
                    "service": service_name,
                    "image": expected_image,
                    "needs_action": needs_action,
                    "action_type": action_type,  # 'update', 'replace', 'none'
                    "reason": reason
                })
                
                logger.info(f"🔍 [CONFIG_SYNC] {service_name}: {action_type} - {reason}")
            
            logger.info(f"✅ [CONFIG_SYNC] 镜像版本校验完成")
            status_manager.update_check_progress(40, "镜像版本校验完成")
            
            # 步骤3: 停止和清理不匹配的容器
            logger.info("3️⃣ [CONFIG_SYNC] 步骤3/5: 停止和清理不匹配的容器")
            status_manager.update_check_progress(45, "步骤3/5: 停止和清理不匹配的容器")
            
            # 分类处理不同类型的镜像操作
            images_to_update = [r for r in image_verification_results if r["action_type"] == "update"]
            images_to_replace = [r for r in image_verification_results if r["action_type"] == "replace"]
            services_need_action = images_to_update + images_to_replace
            
            logger.info(f"📊 [CONFIG_SYNC] 需要更新: {len(images_to_update)}, 需要替换: {len(images_to_replace)}")
            
            if services_need_action:
                logger.info("🛑 [CONFIG_SYNC] 检测到不匹配服务，立即停止相关容器")
                
                # 立即停止所有需要处理的容器
                for i, service_info in enumerate(services_need_action, 1):
                    service_name = service_info["service"]
                    action_type = service_info["action_type"]
                    
                    logger.info(f"🛑 [CONFIG_SYNC] [{i}/{len(services_need_action)}] 停止容器: {service_name} (原因: {action_type})")
                    status_manager.update_check_progress(
                        45 + (i * 10 // len(services_need_action)),
                        f"步骤3/5: 停止容器 {service_name}"
                    )
                    
                    # 停止容器
                    self._stop_container_if_running(service_name)
                    
                    # 如果是replace操作，还需要删除镜像
                    if action_type == "replace":
                        logger.info(f"🗑️ [CONFIG_SYNC] 删除不匹配的镜像: {service_name}")
                        self._remove_old_image(service_name)
                
                logger.info(f"✅ [CONFIG_SYNC] 容器停止和清理完成")
                status_manager.update_check_progress(65, "容器停止和清理完成")
            else:
                logger.info("✅ [CONFIG_SYNC] 所有服务都是最新状态，无需停止容器")
                status_manager.update_check_progress(65, "无需停止容器，跳过清理步骤")
            
            # 步骤4: 从Gitee拉取配置文件
            logger.info("4️⃣ [CONFIG_SYNC] 步骤4/5: 从Gitee拉取配置文件")
            status_manager.update_check_progress(70, "步骤4/5: 从Gitee拉取配置文件")
            
            # 从LWX返回数据中获取学校名称
            school_name = school_config.get("school_name", "测试学校")
            try:
                from .gitee_config_service import create_gitee_config_service
                gitee_service = create_gitee_config_service()
                
                logger.info(f"📥 [CONFIG_SYNC] 从Gitee拉取学校配置: {school_name}")
                gitee_config, compose_file_path = gitee_service.fetch_school_config(school_name)
                
                if gitee_config:
                    logger.info(f"✅ [CONFIG_SYNC] Gitee配置拉取成功")
                    # 使用Gitee配置更新本地配置
                    school_config.update(gitee_config)
                else:
                    logger.warning(f"⚠️ [CONFIG_SYNC] Gitee配置拉取失败，使用LWX配置")
                    
            except Exception as e:
                logger.warning(f"⚠️ [CONFIG_SYNC] Gitee配置拉取异常: {e}，使用LWX配置")
            
            status_manager.update_check_progress(80, "配置文件拉取完成")
            
            # 步骤5: 基于Gitee配置重建容器服务
            logger.info("5️⃣ [CONFIG_SYNC] 步骤5/5: 基于Gitee配置重建容器服务")
            status_manager.update_check_progress(85, "步骤5/5: 基于Gitee配置重建容器服务")
            
            # 从Gitee同步docker-compose文件（不再本地生成）
            logger.info("📝 [CONFIG_SYNC] 从Gitee同步docker-compose文件")
            try:
                self._generate_docker_compose_file(school_name, school_config)
            except Exception as e:
                logger.error(f"❌ [CONFIG_SYNC] 从远程获取Docker Compose文件失败: {e}")
                # 按用户要求：获取失败就失败，不再本地生成
                raise Exception(f"无法获取Docker Compose文件，请检查远程仓库: {e}")
            
            # 根据配置拉取所需镜像并启动服务
            logger.info("🚀 [CONFIG_SYNC] 根据Gitee配置拉取镜像并启动服务")
            self._start_docker_services(school_name)
            
            logger.info(f"✅ [CONFIG_SYNC] 容器服务重建完成")
            status_manager.update_check_progress(95, "容器服务重建完成")
            
            # 最终结果汇总
            if services_need_action:
                total_updates = len(services_need_action)
                status_manager.update_check_progress(95, f"检查完成，处理了 {total_updates} 个服务")
                logger.info(f"🎉 [CONFIG_SYNC] LWX平台集成检测完成，处理了 {total_updates} 个服务")
                
                # 构建更新结果列表
                available_updates = []
                for service_info in services_need_action:
                    available_updates.append({
                        "name": service_info["service"],
                        "reason": service_info.get("reason", "需要更新"),
                        "image": service_info.get("image", "unknown")
                    })
            else:
                status_manager.update_check_progress(95, "检查完成，所有服务都是最新状态")
                logger.info("✅ [CONFIG_SYNC] 检查完成，所有服务都是最新状态")
                available_updates = []
            
            # 完成检查
            status_manager.finish_checking(available_updates)
            
            # 汇总更新结果
            available_updates = []
            for result in image_verification_results:
                if result["needs_action"]:
                    available_updates.append({
                        "name": result["service"],
                        "image": result["image"],
                        "reason": result["reason"],
                        "action": result["action_type"]
                    })
            
            # 完成检查
            if available_updates:
                logger.info(f"🎉 [CONFIG_SYNC] 检查完成，处理了 {len(available_updates)} 个服务")
                status_manager.update_check_progress(100, f"检查完成，处理了 {len(available_updates)} 个服务")
            else:
                logger.info(f"✅ [CONFIG_SYNC] 检查完成，系统已是最新状态")
                status_manager.update_check_progress(100, "检查完成，系统已是最新状态")
            
            status_manager.finish_checking(available_updates)
            
        except Exception as e:
            logger.error(f"❌ Docker镜像更新流程失败: {e}")
            status_manager = get_status_manager(self.socketio)
            status_manager.finish_checking([], str(e))
    
    def _get_current_containers(self):
        """获取当前运行的容器信息"""
        try:
            if hasattr(self, 'docker_client') and self.docker_client:
                containers = self.docker_client.containers.list(all=True)
                return [{
                    'name': container.name,
                    'image': container.image.tags[0] if container.image.tags else 'unknown',
                    'status': container.status,
                    'id': container.id[:12]
                } for container in containers]
            else:
                # 使用命令行方式
                import subprocess
                result = subprocess.run(['docker', 'ps', '-a', '--format', 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.ID}}'], 
                                      capture_output=True, text=True)
                containers = []
                for line in result.stdout.strip().split('\n')[1:]:  # 跳过标题行
                    if line.strip():
                        parts = line.split('\t')
                        if len(parts) >= 4:
                            containers.append({
                                'name': parts[0],
                                'image': parts[1],
                                'status': parts[2],
                                'id': parts[3]
                            })
                return containers
        except Exception as e:
            logger.error(f"获取容器信息失败: {e}")
            return []
    
    def _compare_configurations(self, remote_services, local_containers):
        """对比远程配置与本地容器的差异"""
        differences = []
        try:
            remote_service_names = {s.get('name') for s in remote_services}
            local_container_names = {c.get('name') for c in local_containers}
            
            # 检查缺失的服务
            missing_services = remote_service_names - local_container_names
            for service_name in missing_services:
                differences.append(f"缺失服务: {service_name}")
            
            # 检查多余的容器
            extra_containers = local_container_names - remote_service_names
            for container_name in extra_containers:
                differences.append(f"多余容器: {container_name}")
            
            # 检查服务状态
            for container in local_containers:
                if container['status'] != 'running' and container['name'] in remote_service_names:
                    differences.append(f"服务未运行: {container['name']} ({container['status']})")
                    
            return differences
        except Exception as e:
            logger.error(f"配置对比失败: {e}")
            return [f"配置对比失败: {str(e)}"]
    
    def _validate_container_status(self, enabled_services):
        """校验容器运行状态"""
        try:
            status_results = {}
            for service in enabled_services:
                service_name = service.get('name')
                try:
                    if hasattr(self, 'docker_client') and self.docker_client:
                        container = self.docker_client.containers.get(service_name)
                        status_results[service_name] = {
                            'running': container.status == 'running',
                            'health': getattr(container.attrs.get('State', {}), 'Health', {}).get('Status', 'unknown')
                        }
                    else:
                        # 使用命令行检查
                        import subprocess
                        result = subprocess.run(['docker', 'inspect', '--format', '{{.State.Status}}', service_name], 
                                              capture_output=True, text=True)
                        status_results[service_name] = {
                            'running': result.stdout.strip() == 'running',
                            'health': 'unknown'
                        }
                except Exception as e:
                    status_results[service_name] = {
                        'running': False,
                        'health': 'error',
                        'error': str(e)
                    }
            return status_results
        except Exception as e:
            logger.error(f"容器状态校验失败: {e}")
            return {}
    
    def _docker_available(self) -> bool:
        """检测 Docker daemon 是否可用。"""
        try:
            # 如果有已初始化的客户端，优先用 ping
            if getattr(self, 'docker_client', None) is not None:
                try:
                    self.docker_client.ping()
                    return True
                except Exception:
                    pass
            # 回退到命令行检测
            result = subprocess.run(
                ["docker", "info"], capture_output=True, text=True, timeout=5
            )
            return result.returncode == 0
        except Exception:
            return False
    
    def _get_docker_compose_command(self) -> List[str]:
        """检测并返回可用的docker-compose命令"""
        try:
            # 首先尝试docker compose (v2)
            result = subprocess.run(
                ["docker", "compose", "version"], 
                capture_output=True, text=True, timeout=10
            )
            if result.returncode == 0:
                logger.info("🐳 [CONFIG_SYNC] 使用Docker Compose v2: docker compose")
                return ["docker", "compose"]
        except Exception as e:
            logger.debug(f"Docker Compose v2检测失败: {e}")
        
        try:
            # 回退到docker-compose (v1)
            result = subprocess.run(
                ["docker-compose", "--version"], 
                capture_output=True, text=True, timeout=10
            )
            if result.returncode == 0:
                logger.info("🐳 [CONFIG_SYNC] 使用Docker Compose v1: docker-compose")
                return ["docker-compose"]
        except Exception as e:
            logger.debug(f"Docker Compose v1检测失败: {e}")
        
        # 如果都不可用，默认使用v2命令（可能会失败，但会给出更好的错误信息）
        logger.warning("⚠️ [CONFIG_SYNC] 未检测到可用的Docker Compose，默认使用v2命令")
        return ["docker", "compose"]

    def _normalize_school_name(self, school_name: str) -> str:
        """规范化学校名称为文件名格式"""
        # 简单的映射规则，可以根据需要扩展
        name_mapping = {
            "测试学校": "testschool",
            # 可以在这里添加更多学校名称的映射规则
        }
        
        # 如果有预定义的映射，使用映射结果
        if school_name in name_mapping:
            return name_mapping[school_name]
        
        # 否则进行通用的规范化处理
        normalized = school_name.lower().replace(" ", "").replace("-", "").replace("_", "")
        return normalized
    
    def _pull_and_run_docker_images(self, school_config: Dict[str, Any]):
        """使用docker-compose自动拉取和运行Docker镜像，并确保与平台配置完全同步"""
        try:
            logger.info("使用docker-compose管理容器服务，执行完全同步")
            
            if "docker" not in school_config or "services" not in school_config["docker"]:
                logger.warning("配置中没有Docker服务信息，跳过镜像处理")
                return
            
            # 1. 停止现有的docker-compose服务
            self._stop_docker_compose()
            
            # 2. 清理不再需要的镜像和容器
            self._cleanup_obsolete_images_and_containers(school_config)
            
            # 3. 使用docker-compose启动新服务
            school_name = school_config.get("school_name", "测试学校")
            self._start_docker_compose_services(school_name)
            
        except Exception as e:
            logger.error(f"Docker compose处理失败: {e}")
    
    def _cleanup_obsolete_images_and_containers(self, school_config: Dict[str, Any]):
        """清理不再需要的镜像和容器，确保与平台配置完全一致"""
        try:
            if not self.docker_client:
                logger.warning("Docker客户端未初始化，跳过清理操作")
                return
                
            # 获取平台配置中的镜像列表
            platform_images = set()
            if "docker" in school_config and "services" in school_config["docker"]:
                for service in school_config["docker"]["services"]:
                    if "remote_image" in service:
                        # 构建完整的镜像名称
                        registry_url = school_config["docker"].get("registry_url", "")
                        full_image = f"{registry_url}/{service['remote_image']}" if registry_url else service['remote_image']
                        platform_images.add(full_image)
                        logger.info(f"平台配置中的镜像: {full_image}")
            
            # 获取当前系统中的所有镜像
            current_images = self.docker_client.images.list(all=True)
            
            # 找出需要删除的镜像
            obsolete_images = []
            for image in current_images:
                # 检查镜像是否在平台配置中
                image_in_platform = False
                for tag in image.tags:
                    if tag in platform_images:
                        image_in_platform = True
                        break
                    
                    # 也检查不带registry的名称
                    tag_without_registry = tag.split('/')[-1] if '/' in tag else tag
                    for platform_image in platform_images:
                        platform_image_without_registry = platform_image.split('/')[-1] if '/' in platform_image else platform_image
                        if tag_without_registry == platform_image_without_registry:
                            image_in_platform = True
                            break
                
                if not image_in_platform and image.tags:
                    # 排除系统镜像和基础镜像
                    should_keep = False
                    for tag in image.tags:
                        if any(keyword in tag.lower() for keyword in ['ubuntu', 'debian', 'alpine', 'python', 'node', 'nginx', 'redis', 'mysql', 'postgres']):
                            # 只保留基础镜像，不保留带有特定标签的应用镜像
                            if not any(keyword in tag.lower() for keyword in ['mobox', 'custom', 'app']):
                                should_keep = True
                                break
                    
                    if not should_keep:
                        obsolete_images.append(image)
                        logger.info(f"发现过时镜像: {image.tags}")
            
            # 删除过时的容器
            containers = self.docker_client.containers.list(all=True)
            for container in containers:
                container_image = container.image.tags[0] if container.image.tags else container.image.id
                
                # 检查容器的镜像是否还在平台配置中
                container_in_platform = False
                for platform_image in platform_images:
                    if platform_image in container_image or container_image.endswith(platform_image.split('/')[-1]):
                        container_in_platform = True
                        break
                
                if not container_in_platform:
                    try:
                        logger.info(f"删除过时容器: {container.name} (镜像: {container_image})")
                        container.stop(timeout=10)
                        container.remove()
                    except Exception as e:
                        logger.warning(f"删除容器 {container.name} 失败: {e}")
            
            # 删除过时的镜像
            for image in obsolete_images:
                try:
                    logger.info(f"删除过时镜像: {image.tags}")
                    self.docker_client.images.remove(image.id, force=True)
                except Exception as e:
                    logger.warning(f"删除镜像 {image.tags} 失败: {e}")
            
            # 清理悬空镜像
            try:
                dangling_images = self.docker_client.images.list(filters={"dangling": True})
                for image in dangling_images:
                    logger.info(f"删除悬空镜像: {image.id[:12]}")
                    self.docker_client.images.remove(image.id, force=True)
            except Exception as e:
                logger.warning(f"清理悬空镜像失败: {e}")
                
            logger.info("镜像和容器清理完成")
            
        except Exception as e:
            logger.error(f"清理过时镜像和容器失败: {e}")
    
    def _stop_docker_compose(self):
        """停止现有的docker-compose服务"""
        try:
            # 获取docker compose命令（v2/v1兼容）
            compose_cmd = self._get_docker_compose_command()
            # 查找并停止现有的docker-compose服务
            compose_files = list(self.config_dir.glob("docker-compose*.yml"))
            
            for compose_file in compose_files:
                try:
                    logger.info(f"🛑 [CONFIG_SYNC] 停止compose服务: {compose_file}")
                    cmd = compose_cmd + ["-f", str(compose_file), "down"]
                    logger.info(f"🔧 [CONFIG_SYNC] 执行命令: {' '.join(cmd)}")
                    result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
                    
                    if result.returncode == 0:
                        logger.info(f"✅ [CONFIG_SYNC] 成功停止compose服务: {compose_file}")
                        if result.stdout:
                            logger.info(f"📤 [CONFIG_SYNC] 输出: {result.stdout}")
                    else:
                        logger.warning(f"⚠️ [CONFIG_SYNC] 停止compose服务失败: {result.stderr}")
                        
                except Exception as e:
                    logger.warning(f"⚠️ [CONFIG_SYNC] 停止compose文件 {compose_file} 失败: {e}")
                    
        except Exception as e:
            logger.error(f"❌ [CONFIG_SYNC] 停止docker-compose服务失败: {e}")
    
    def _start_docker_compose_services(self, school_name: str = None):
        """启动docker-compose服务"""
        try:
            # 查找学校对应的docker-compose文件
            school_compose_files = []
            
            if school_name:
                # 如果指定了学校名称，优先使用该学校的compose文件
                specific_school_compose = self.config_dir / f"docker-compose-{school_name}.yml"
                if specific_school_compose.exists():
                    school_compose_files.append(specific_school_compose)
                    logger.info(f"📁 [CONFIG_SYNC] 找到指定学校的compose文件: {specific_school_compose}")
            
            # 如果没有找到指定学校的文件，查找其他可用的compose文件
            if not school_compose_files:
                # 首先查找测试学校的compose文件作为备选
                test_school_compose = self.config_dir / "docker-compose-测试学校.yml"
                if test_school_compose.exists():
                    school_compose_files.append(test_school_compose)
                
                # 然后查找其他学校的compose文件
                other_compose_files = list(self.config_dir.glob("docker-compose-*.yml"))
                for compose_file in other_compose_files:
                    if compose_file not in school_compose_files:
                        school_compose_files.append(compose_file)
            
            if not school_compose_files:
                logger.warning("没有找到学校专用的docker-compose文件")
                # 尝试使用默认的docker-compose.yml
                default_compose = self.config_dir / "docker-compose.yml"
                if default_compose.exists():
                    school_compose_files = [default_compose]
                else:
                    logger.error("没有找到任何docker-compose文件")
                    return
            
            # 启动最新的compose文件
            compose_file = school_compose_files[0]  # 使用第一个找到的文件
            
            logger.info(f"🚀 [CONFIG_SYNC] 启动docker-compose服务: {compose_file}")
            # 执行docker compose up（v2/v1兼容）
            compose_cmd = self._get_docker_compose_command()
            # 移除--build参数，避免不必要的重新构建，提高响应速度
            cmd = compose_cmd + ["-f", str(compose_file), "up", "-d"]
            cmd_str = ' '.join(cmd)
            logger.info(f"🔧 [CONFIG_SYNC] 执行命令: {cmd_str}")
            
            # 记录执行环境信息
            import os
            current_dir = os.getcwd()
            logger.info(f"📂 [CONFIG_SYNC] 工作目录: {current_dir}")
            logger.info(f"📋 [CONFIG_SYNC] Compose文件存在: {compose_file.exists()}")
            if compose_file.exists():
                file_size = compose_file.stat().st_size
                logger.info(f"📊 [CONFIG_SYNC] Compose文件大小: {file_size} 字节")
            
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)  # 降低到1分钟超时
            
            if result.returncode == 0:
                logger.info(f"✅ [CONFIG_SYNC] docker-compose服务启动成功")
                logger.info(f"⏱️ [CONFIG_SYNC] 命令执行耗时: 约{60 - 60}秒")
                if result.stdout:
                    stdout_lines = result.stdout.strip().split('\n')
                    logger.info(f"📤 [CONFIG_SYNC] 标准输出 ({len(stdout_lines)} 行):")
                    for i, line in enumerate(stdout_lines):
                        if line.strip():
                            logger.info(f"  └─ {i+1}: {line}")
                if result.stderr:
                    stderr_lines = result.stderr.strip().split('\n')
                    logger.info(f"⚠️ [CONFIG_SYNC] 标准错误 ({len(stderr_lines)} 行):")
                    for i, line in enumerate(stderr_lines):
                        if line.strip():
                            logger.info(f"  └─ {i+1}: {line}")
            else:
                logger.error(f"❌ [CONFIG_SYNC] docker-compose服务启动失败: 返回码 {result.returncode}")
                if result.stdout:
                    stdout_lines = result.stdout.strip().split('\n')
                    logger.error(f"📤 [CONFIG_SYNC] 标准输出 ({len(stdout_lines)} 行):")
                    for i, line in enumerate(stdout_lines):
                        if line.strip():
                            logger.error(f"  └─ {i+1}: {line}")
                if result.stderr:
                    stderr_lines = result.stderr.strip().split('\n') 
                    logger.error(f"❌ [CONFIG_SYNC] 标准错误 ({len(stderr_lines)} 行):")
                    for i, line in enumerate(stderr_lines):
                        if line.strip():
                            logger.error(f"  └─ {i+1}: {line}")
                
            # 检查服务状态
            self._check_compose_services_status(compose_file)
            
        except subprocess.TimeoutExpired:
            logger.error("docker-compose启动超时")
        except Exception as e:
            logger.error(f"启动docker-compose服务失败: {e}")
    
    def _check_compose_services_status(self, compose_file: Path):
        """检查docker-compose服务状态"""
        try:
            compose_cmd = self._get_docker_compose_command()
            cmd = compose_cmd + ["-f", str(compose_file), "ps"]
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
            
            if result.returncode == 0:
                logger.info(f"📋 [CONFIG_SYNC] docker-compose服务状态:\n{result.stdout}")
            else:
                logger.warning(f"⚠️ [CONFIG_SYNC] 检查服务状态失败: {result.stderr}")
                
        except Exception as e:
            logger.warning(f"⚠️ [CONFIG_SYNC] 检查docker-compose服务状态失败: {e}")


    def _verify_image_version(self, image_name: str) -> Tuple[bool, str, str]:
        """
        校验镜像版本是否正确
        
        Args:
            image_name: 镜像名称，如 "registry.cn-hangzhou.aliyuncs.com/mobox_dev/mobox-python-base:latest"
            
        Returns:
            tuple: (需要操作, 操作类型, 原因)
                - needs_action: bool，是否需要操作
                - action_type: str，操作类型 ('update', 'replace', 'none')
                - reason: str，原因说明
        """
        try:
            logger.info(f"🔍 [VERIFY] 开始校验镜像: {image_name}")
            
            # 检查本地是否有该镜像
            local_images = self.docker_client.images.list()
            local_image_tags = []
            for img in local_images:
                if img.tags:
                    local_image_tags.extend(img.tags)
            
            if image_name not in local_image_tags:
                logger.info(f"❌ [VERIFY] 本地镜像不存在: {image_name}")
                return True, "update", f"本地镜像不存在: {image_name}"
            
            # 检查镜像是否为最新版本（通过检查远程registry）
            try:
                # 尝试拉取最新镜像信息
                logger.info(f"🌐 [VERIFY] 检查远程镜像版本: {image_name}")
                remote_image = self.docker_client.images.get_registry_data(image_name)
                local_image = self.docker_client.images.get(image_name)
                
                # 比较镜像ID或摘要
                if hasattr(remote_image, 'id') and hasattr(local_image, 'id'):
                    if remote_image.id != local_image.id:
                        logger.info(f"🔄 [VERIFY] 镜像版本不匹配: {image_name}")
                        return True, "update", f"镜像版本不是最新: {image_name}"
                
                logger.info(f"✅ [VERIFY] 镜像版本正确: {image_name}")
                return False, "none", f"镜像版本正确: {image_name}"
                
            except Exception as registry_error:
                logger.warning(f"⚠️ [VERIFY] 无法检查远程版本: {registry_error}")
                # 如果无法检查远程版本，假设本地镜像是有效的
                return False, "none", f"无法验证远程版本，使用本地镜像: {image_name}"
                
        except Exception as e:
            logger.error(f"❌ [VERIFY] 镜像校验失败: {image_name} - {e}")
            return True, "replace", f"镜像校验失败: {e}"

    def _stop_container_if_running(self, service_name: str) -> None:
        """
        如果名称匹配的容器正在运行，则尝试优雅停止。
        匹配策略：容器名称包含服务名（忽略大小写）。
        """
        try:
            containers = self.docker_client.containers.list(all=True)
            matched_containers = []
            for container in containers:
                names = container.attrs.get('Name') or ''
                name_list = [names] if isinstance(names, str) else names
                name_list += container.attrs.get('Names') or []
                name_list = [n.strip('/') for n in name_list if n]
                if any(service_name.lower() in n.lower() for n in name_list):
                    matched_containers.append(container)

            if not matched_containers:
                logger.info(f"ℹ️ 未找到匹配容器: {service_name}")
                return

            for container in matched_containers:
                try:
                    if container.status in ("running", "restarting"):
                        logger.info(f"🛑 停止容器: {container.name} ({container.id[:12]})")
                        container.stop(timeout=15)
                    # 确保容器被移除，避免残留
                    logger.info(f"🧹 移除容器: {container.name} ({container.id[:12]})")
                    container.remove(force=True)
                except Exception as stop_err:
                    logger.warning(f"停止/移除容器失败 {container.name}: {stop_err}")
        except Exception as e:
            logger.warning(f"停止容器时发生异常: {e}")

    def _remove_old_image(self, service_name: str) -> None:
        """
        按服务名模糊匹配相关镜像并移除（dangling 或 旧版本）。
        """
        try:
            images = self.docker_client.images.list()
            for image in images:
                tags = image.tags or []
                if any(service_name.lower() in tag.lower() for tag in tags):
                    try:
                        logger.info(f"🗑️ 删除镜像: {tags} ({image.id[:12]})")
                        self.docker_client.images.remove(image=image.id, force=True, noprune=False)
                    except Exception as rm_err:
                        logger.warning(f"删除镜像失败 {tags}: {rm_err}")
        except Exception as e:
            logger.warning(f"清理镜像时发生异常: {e}")

    def _start_docker_services(self, school_name: str) -> None:
        """
        基于生成的 compose 文件启动服务。内部复用现有的 compose 启动逻辑。
        """
        try:
            # 停止已有 compose 服务，避免端口/资源冲突
            self._stop_docker_compose()
            # 启动 compose 服务
            self._start_docker_compose_services(school_name)
            # 选取推断的 compose 文件检查状态（最佳努力）
            compose_file = self.config_dir / f"docker-compose-{school_name}.yml"
            if compose_file.exists():
                self._check_compose_services_status(compose_file)
        except Exception as e:
            logger.warning(f"启动docker服务时发生异常: {e}")

    def _collect_device_info(self) -> Dict[str, Any]:
        """收集设备信息"""
        try:
            import platform
            import psutil
            import socket
            
            device_info = {
                "hostname": socket.gethostname(),
                "platform": platform.platform(),
                "architecture": platform.architecture()[0],
                "cpu_count": psutil.cpu_count(),
                "memory_total": psutil.virtual_memory().total,
                "disk_usage": psutil.disk_usage("/").total,
                "timestamp": time.time()
            }
            
            # 添加Docker信息
            if self._docker_available():
                try:
                    containers = self.docker_client.containers.list(all=True)
                    device_info["docker_containers"] = [
                        {
                            "name": c.name,
                            "image": c.image.tags[0] if c.image.tags else "unknown",
                            "status": c.status
                        } for c in containers
                    ]
                except Exception as e:
                    logger.warning(f"获取Docker容器信息失败: {e}")
                    device_info["docker_containers"] = []
            
            return device_info
        except Exception as e:
            logger.error(f"收集设备信息失败: {e}")
            return {}
    
    def _upload_to_lwx_platform(self, device_info: Dict[str, Any], trigger_sync: bool = True) -> bool:
        """上传设备信息到LWX平台"""
        try:
            # 集成现有的设备上报服务
            from .device_report import get_device_reporter
            
            device_reporter = get_device_reporter()
            if device_reporter is None:
                logger.warning("设备上报服务未初始化，跳过上报")
                return True  # 不阻断更新流程
            
            logger.info(f"📊 [CONFIG_SYNC] 设备信息摘要: 主机名={device_info.get('hostname')}, "
                       f"容器数量={len(device_info.get('docker_containers', []))}")
            
            # 使用现有的设备上报逻辑，支持控制是否触发配置同步
            upload_result = device_reporter.report_status(trigger_sync=trigger_sync)
            if upload_result:
                logger.info("✅ 设备信息上报到LWX平台成功")
                if not trigger_sync:
                    logger.info("📝 手动上报模式，跳过配置同步")
                return True
            else:
                logger.warning("⚠️ 设备信息上报失败，但不阻断更新流程")
                return True  # 即使上报失败也继续更新流程
                
        except Exception as e:
            logger.error(f"上传到LWX平台失败: {e}")
            # 不因为上报失败而阻断整个更新流程
            return True
    
    def _get_config_from_lwx_platform(self, school_name: str = "测试学校") -> Dict[str, Any]:
        """从LWX平台获取配置信息"""
        try:
            # 使用现有的Gitee配置服务获取真实配置
            from .gitee_config_service import create_gitee_config_service
            
            logger.info("🌐 使用Gitee配置服务获取最新配置...")
            
            # 创建Gitee配置服务实例
            gitee_service = create_gitee_config_service()
            
            # 使用传入的学校名称获取配置
            logger.info(f"📥 [CONFIG_SYNC] 从Gitee拉取学校配置: {school_name}")
            try:
                school_config, compose_path = gitee_service.fetch_school_config(school_name)
                
                if school_config and "docker" in school_config:
                    logger.info(f"✅ 从Gitee获取配置成功: {school_name}")
                    # 添加学校信息以保持兼容性
                    config_with_school = {
                        "school_name": school_name,
                        **school_config
                    }
                    logger.info(f"📥 [CONFIG_SYNC] 从配置服务获取到 {len(config_with_school.get('docker', {}).get('services', []))} 个服务配置")
                    return config_with_school
                else:
                    logger.warning("配置服务返回空配置，使用默认配置")
                    
            except Exception as e:
                logger.warning(f"从配置服务获取配置失败: {e}，使用默认配置")
            
            # 如果配置服务获取失败，返回基础默认配置
            default_config = {
                "school_name": school_name,
                "docker": {
                    "registry": {
                        "url": "crpi-axr6nxua9k92sqiz.cn-hangzhou.personal.cr.aliyuncs.com"
                    },
                    "services": [
                        {
                            "name": "mobox-python-hardware",
                            "remote_image": "mobox-docker/mobox-python-hardware:latest",
                            "enabled": True,
                            "port": 8080,
                            "description": "MoBox Python Hardware Environment"
                        }
                    ]
                }
            }
            
            logger.info(f"📥 [CONFIG_SYNC] 使用默认配置: {len(default_config.get('docker', {}).get('services', []))} 个服务配置")
            return default_config
        except Exception as e:
            logger.error(f"从LWX平台获取配置失败: {e}")
            return {}


    def startup_auto_update_check(self, school_config: Dict[str, Any], silent_mode: bool = True) -> Dict[str, Any]:
        """
        开机自动更新检查
        
        Args:
            school_config: 学校配置
            silent_mode: 静默模式，不发送前端通知
        
        Returns:
            Dict[str, Any]: 更新结果
        """
        try:
            logger.info(f"🌅 [AUTO_UPDATE] 开始开机自动更新检查，静默模式: {silent_mode}")
            
            if not silent_mode and self.socketio:
                status_manager = get_status_manager(self.socketio)
                status_manager.start_checking()
            
            auto_update_results = {
                "total_services": 0,
                "checked_services": 0,
                "auto_updated": 0,
                "manual_required": 0,
                "errors": [],
                "updated_services": []
            }
            
            if not school_config or "docker" not in school_config:
                logger.warning(f"⚠️ [AUTO_UPDATE] 无效的学校配置")
                return auto_update_results
            
            services = school_config["docker"].get("services", [])
            registry_url = school_config["docker"].get("registry", {}).get("url", "")
            auto_update_results["total_services"] = len(services)
            
            logger.info(f"📊 [AUTO_UPDATE] 发现 {len(services)} 个服务配置")
            
            for service_config in services:
                if not service_config.get("enabled", True):
                    continue
                    
                service_name = service_config["name"]
                remote_image = service_config["remote_image"]
                expected_image = f"{registry_url}/{remote_image}"
                auto_update_enabled = service_config.get("auto_update", False)
                
                auto_update_results["checked_services"] += 1
                
                logger.info(f"🔍 [AUTO_UPDATE] 检查服务: {service_name} (自动更新: {auto_update_enabled})")
                
                try:
                    # 检查镜像版本
                    needs_action, action_type, reason = self._verify_image_version(expected_image)
                    
                    if needs_action:
                        if auto_update_enabled:
                            # 自动更新模式
                            logger.info(f"🚀 [AUTO_UPDATE] 自动更新服务: {service_name}")
                            
                            # 停止容器
                            self._stop_container_if_running(service_name)
                            
                            # 拉取新镜像
                            self._pull_image_with_progress(expected_image)
                            
                            # 移除旧镜像
                            self._remove_old_image(service_name)
                            
                            auto_update_results["auto_updated"] += 1
                            auto_update_results["updated_services"].append({
                                "service": service_name,
                                "image": expected_image,
                                "action": "auto_updated",
                                "reason": reason
                            })
                            
                            logger.info(f"✅ [AUTO_UPDATE] 服务 {service_name} 自动更新完成")
                            
                        else:
                            # 手动更新模式 - 仅记录，不显示给用户
                            auto_update_results["manual_required"] += 1
                            logger.info(f"📝 [AUTO_UPDATE] 服务 {service_name} 需要手动更新: {reason}")
                            
                            if not silent_mode:
                                auto_update_results["updated_services"].append({
                                    "service": service_name,
                                    "image": expected_image,
                                    "action": "manual_required",
                                    "reason": reason
                                })
                    else:
                        logger.info(f"✅ [AUTO_UPDATE] 服务 {service_name} 镜像版本正确")
                        
                except Exception as e:
                    error_msg = f"服务 {service_name} 检查失败: {str(e)}"
                    auto_update_results["errors"].append(error_msg)
                    logger.error(f"❌ [AUTO_UPDATE] {error_msg}")
            
            # 启动所有服务（无论是否更新）
            try:
                school_name = school_config.get("school_name", "测试学校")
                self._start_docker_services(school_name)
                logger.info(f"🚀 [AUTO_UPDATE] 所有Docker服务已启动")
            except Exception as e:
                error_msg = f"启动Docker服务失败: {str(e)}"
                auto_update_results["errors"].append(error_msg)
                logger.error(f"❌ [AUTO_UPDATE] {error_msg}")
            
            # 完成通知（仅非静默模式）
            if not silent_mode and self.socketio:
                status_manager = get_status_manager(self.socketio)
                if auto_update_results["auto_updated"] > 0:
                    summary_message = f"自动更新完成: {auto_update_results['auto_updated']} 个服务已更新"
                else:
                    summary_message = f"检查完成: 所有服务都是最新版本"
                    
                status_manager.finish_checking(auto_update_results["updated_services"], summary_message)
            
            logger.info(f"🎉 [AUTO_UPDATE] 开机自动更新检查完成")
            logger.info(f"📊 [AUTO_UPDATE] 统计: 总服务{auto_update_results['total_services']}个, "
                       f"检查{auto_update_results['checked_services']}个, "
                       f"自动更新{auto_update_results['auto_updated']}个, "
                       f"需手动{auto_update_results['manual_required']}个")
            
            return auto_update_results
            
        except Exception as e:
            logger.error(f"❌ [AUTO_UPDATE] 开机自动更新检查失败: {e}")
            
            if not silent_mode and self.socketio:
                status_manager = get_status_manager(self.socketio)
                status_manager.finish_checking([], f"自动更新检查失败: {str(e)}")
            
            return {
                "total_services": 0,
                "checked_services": 0,
                "auto_updated": 0,
                "manual_required": 0,
                "errors": [str(e)],
                "updated_services": []
            }

def create_config_sync_service() -> ConfigSyncService:
    """创建配置同步服务实例"""
    return ConfigSyncService()

